﻿// -----------------------------------------------------------------------
// <copyright file="PushApplicationService.cs" company="Zen Design Corp">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace Zen.BlackBerry.Push.AppManagement
{
	using System;
	using System.Collections.Generic;
	using System.Threading.Tasks;
	using System.Threading.Tasks.Dataflow;
	using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
	using Microsoft.Practices.ServiceLocation;
	using Microsoft.Practices.Unity;
	using Zen.BlackBerry.Configuration;
	using Zen.BlackBerry.Push.Stats;
	using Zen.BlackBerry.Subscription;
	using Zen.BlackBerry.Subscription.Web;

	/// <summary>
	/// Implementation of the <see cref="IPushApplicationService"/> interface.
	/// </summary>
	public class PushApplicationService : IPushApplicationService
	{
		private class UnsubscriberHelper
		{
			private class UnsubscribeBlock
			{
				public string PushApplicationId
				{
					get;
					set;
				}

				public SubscriberStatus SubscriberStatus
				{
					get;
					set;
				}

				public int BlockIndex
				{
					get;
					set;
				}

				public int TotalResultsFound
				{
					get;
					set;
				}
			}

			private IPushProperties _pushProperties;
			private ISubscriptionService _subscriptionService;
			private ExceptionManager _exceptionManager;

			private TransformManyBlock<UnsubscribeBlock, SubscriberPartial> _blockQuery;
			private ActionBlock<SubscriberPartial> _unsubscribeQueue;
			private IDisposable _blockToUnsubscribeLinkage;

			public UnsubscriberHelper(
				IPushProperties pushProperties,
				ISubscriptionService subscriptionService,
				ExceptionManager exceptionManager)
			{
				_pushProperties = pushProperties;
				_subscriptionService = subscriptionService;
				_exceptionManager = exceptionManager;
				_blockQuery = new TransformManyBlock<UnsubscribeBlock, SubscriberPartial>(
					(block) =>
					{
						int maxResults = _pushProperties.SubscriptionFindMaxResults;
						int startIndex = block.BlockIndex * maxResults;
						int endIndex = Math.Min(block.TotalResultsFound, startIndex + maxResults) - 1;

						return _subscriptionService.FindByAppIdAndStatus(
							block.PushApplicationId, block.SubscriberStatus, startIndex, endIndex);
					},
					new ExecutionDataflowBlockOptions
					{
						MaxDegreeOfParallelism = 2
					});
				_unsubscribeQueue = new ActionBlock<SubscriberPartial>(
					(sub) => ExecuteUnsubscribe(sub),
					new ExecutionDataflowBlockOptions
					{
						MaxDegreeOfParallelism = 3
					});
				_blockToUnsubscribeLinkage = _blockQuery.LinkTo(
					_unsubscribeQueue,
					new DataflowLinkOptions
					{
						PropagateCompletion = true
					});
			}

			public void Unsubscribe(string pushApplicationId, SubscriberStatus status)
			{
				int matches = _subscriptionService.GetSizeByAppIdAndStatus(
					pushApplicationId, status);
				int maxResults = _pushProperties.SubscriptionFindMaxResults;

				// Determine the number of blocks we need to work with
				int blocks = matches / maxResults;
				if ((matches % maxResults) != 0)
				{
					++blocks;
				}

				for (int block = 0; block < blocks; ++block)
				{
					_blockQuery.Post(
						new UnsubscribeBlock
						{
							PushApplicationId = pushApplicationId,
							SubscriberStatus = status,
							BlockIndex = block,
							TotalResultsFound = matches
						});
				}
			}

			public Task AwaitCompletion()
			{
				// Block part must be completed
				_blockQuery.Complete();

				// Return the task from the unsubscriber
				return _unsubscribeQueue.Completion;
			}

			private async Task ExecuteUnsubscribe(SubscriberPartial sub)
			{
				UnsubscribeRequest request = new UnsubscribeRequest(
					sub.PushApplicationId, sub.SubscriberId);
				request.NotifyGateway = true;
				request.IsSystemGenerated = true;
				try
				{
					await _subscriptionService.Unsubscribe(request);
				}
				catch (Exception ex)
				{
					_exceptionManager.HandleException(ex, "BPSS LogNoRethrow");
				}
			}
		}

		private static readonly string ExceptionPolicyName = "BPSS AppManagement";

		/// <summary>
		/// Initializes a new instance of the <see cref="PushApplicationService"/> class.
		/// </summary>
		public PushApplicationService()
		{
		}

		/// <summary>
		/// Gets or sets the application repository.
		/// </summary>
		/// <value>The application repository.</value>
		[Dependency]
		public IPushApplicationRepository ApplicationRepository
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the statistics repository.
		/// </summary>
		/// <value>The statistics repository.</value>
		[Dependency]
		public IPushStatsRepository StatisticsRepository
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the subscription service.
		/// </summary>
		/// <value>The subscription service.</value>
		[Dependency]
		public ISubscriptionService SubscriptionService
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the exception manager.
		/// </summary>
		/// <value>The exception manager.</value>
		[Dependency]
		public ExceptionManager ExceptionManager
		{
			get;
			set;
		}

		#region Validation
		private PushApplication ValidatePushApplicationExists(string pushApplicationId)
		{
			PushApplication app = FindByAppId(pushApplicationId);
			if (app == null)
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.DoesNotExist);
			}
			return app;
		}

		private void ValidateAdd(PushApplication app)
		{
			if (string.IsNullOrWhiteSpace(app.RimApplicationId))
			{
				throw new ArgumentNullException("RimApplicationId", "Application id is null or empty.");
			}
			if (app.RimApplicationId.Length > 50)
			{
				throw new ArgumentException("Application id must be less than 50.", "RimApplicationId");
			}

			if (string.IsNullOrWhiteSpace(app.Name))
			{
				throw new ArgumentNullException("Name", "Name is null or empty.");
			}
			if (app.Name.Length > 50)
			{
				throw new ArgumentException("Name must be less than 50.", "Name");
			}

			PushApplication pa = FindByAppId(app.RimApplicationId);
			if (pa != null)
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.AlreadyExists);
			}
			pa = FindByName(app.Name);
			if (pa != null)
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.NameExists);
			}

			ValidateSharedAddAndUpdate(app);

			if (string.IsNullOrEmpty(app.ModifiedBy))
			{
				throw new ArgumentNullException("ModifiedBy", "ModifiedBy is null or empty.");
			}
			if (app.ModifiedBy.Length > 50)
			{
				throw new ArgumentException("ModifiedBy must be less than 50.", "ModifiedBy");
			}

			ValidateUserNameAndPassword(app);
		}

		private PushApplication ValidateUpdate(PushApplication app)
		{
			if (string.IsNullOrWhiteSpace(app.RimApplicationId))
			{
				throw new ArgumentNullException("Application id is null or empty.");
			}
			if (app.RimApplicationId.Length > 50)
			{
				throw new ArgumentException("Application id must be less than 50.");
			}

			if (string.IsNullOrWhiteSpace(app.Name))
			{
				throw new ArgumentNullException("Name is null or empty.");
			}
			if (app.Name.Length > 50)
			{
				throw new ArgumentException("Name must be less than 50.");
			}

			PushApplication paById = ValidatePushApplicationExists(app.RimApplicationId);
			PushApplication paByName = FindByName(app.Name);
			if (paByName != null && !paByName.RimApplicationId.Equals(app.RimApplicationId))
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.NameExists);
			}

			// Validate type change
			if (paById.Type != app.Type &&
				!(paById.Type != PushApplicationType.PublicEnterprisePush &&
				app.Type == PushApplicationType.PublicEnterprisePush))
			{
				throw new ArgumentException(string.Format(
					"The push application type is not allowed to be changed from {0} to {1}.",
					paById.Type.ToString(),
					app.Type.ToString()),
					"Type");
			}

			bool shouldThrowUserNameAndPasswordError = false;
			if (app.Type == PushApplicationType.PublicPush)
			{
				string passwordFromUpdate = app.PublicPassword;
				string passwordFromStorage = paById.PublicPassword;

				bool hasPublicPasswordChanged =
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage) && !string.Equals(passwordFromUpdate, passwordFromStorage, StringComparison.OrdinalIgnoreCase));

				if (hasPublicPasswordChanged)
				{
					shouldThrowUserNameAndPasswordError = true;
				}
			}
			else if (app.Type == PushApplicationType.EnterprisePush)
			{
				string userNameFromUpdate = app.UserName;
				string userNameFromStorage = paById.UserName;

				bool hasEnterpriseUserNameChanged =
					(!string.IsNullOrWhiteSpace(userNameFromUpdate) && string.IsNullOrWhiteSpace(userNameFromStorage)) ||
					(string.IsNullOrWhiteSpace(userNameFromUpdate) && !string.IsNullOrWhiteSpace(userNameFromStorage)) ||
					(!string.IsNullOrWhiteSpace(userNameFromUpdate) && !string.IsNullOrWhiteSpace(userNameFromStorage) && !string.Equals(userNameFromUpdate, userNameFromStorage, StringComparison.OrdinalIgnoreCase));

				string passwordFromUpdate = app.EnterprisePassword;
				string passwordFromStorage = paById.EnterprisePassword;

				bool hasEnterprisePasswordChanged =
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage) && !string.Equals(passwordFromUpdate, passwordFromStorage, StringComparison.OrdinalIgnoreCase));

				if (hasEnterpriseUserNameChanged || hasEnterprisePasswordChanged)
				{
					shouldThrowUserNameAndPasswordError = true;
				}
			}
			else
			{
				string passwordFromUpdate = app.PublicPassword;
				string passwordFromStorage = paById.PublicPassword;

				bool hasPublicPasswordChanged =
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage) && !string.Equals(passwordFromUpdate, passwordFromStorage, StringComparison.OrdinalIgnoreCase));

				string userNameFromUpdate = app.UserName;
				string userNameFromStorage = paById.UserName;

				bool hasEnterpriseUserNameChanged =
					(!string.IsNullOrWhiteSpace(userNameFromUpdate) && string.IsNullOrWhiteSpace(userNameFromStorage)) ||
					(string.IsNullOrWhiteSpace(userNameFromUpdate) && !string.IsNullOrWhiteSpace(userNameFromStorage)) ||
					(!string.IsNullOrWhiteSpace(userNameFromUpdate) && !string.IsNullOrWhiteSpace(userNameFromStorage) && !string.Equals(userNameFromUpdate, userNameFromStorage, StringComparison.OrdinalIgnoreCase));

				passwordFromUpdate = app.EnterprisePassword;
				passwordFromStorage = paById.EnterprisePassword;

				bool hasEnterprisePasswordChanged =
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage)) ||
					(!string.IsNullOrWhiteSpace(passwordFromUpdate) && !string.IsNullOrWhiteSpace(passwordFromStorage) && !string.Equals(passwordFromUpdate, passwordFromStorage, StringComparison.OrdinalIgnoreCase));

				if (hasPublicPasswordChanged || hasEnterpriseUserNameChanged || hasEnterprisePasswordChanged)
				{
					shouldThrowUserNameAndPasswordError = true;
				}
			}

			if (shouldThrowUserNameAndPasswordError)
			{
				throw new ArgumentException("The ChangePushApplicationPublicCredentials and ChangePushApplicationEnterpriseCredentials methods should be used when updating usernames and passwords, not the UpdatePushApplication method.");
			}

			ValidateSharedAddAndUpdate(app);

			if (string.IsNullOrEmpty(app.ModifiedBy))
			{
				throw new ArgumentNullException("ModifiedBy", "ModifiedBy is null or empty.");
			}
			if (app.ModifiedBy.Length > 50)
			{
				throw new ArgumentException("ModifiedBy must be less than 50.", "ModifiedBy");
			}

			return paById;
		}

		private void ValidateDisable(string pushApplicationId, string modifiedBy)
		{
			if (string.IsNullOrWhiteSpace(pushApplicationId))
			{
				throw new ArgumentNullException("pushApplicationId");
			}
			if (string.IsNullOrWhiteSpace(modifiedBy))
			{
				throw new ArgumentNullException("modifiedBy");
			}

			PushApplication app = ValidatePushApplicationExists(pushApplicationId);
			if (app.Status == PushApplicationStatus.Inactive)
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.Inactive);
			}
		}

		private void ValidateEnable(string pushApplicationId, DateTime? expiryDate, string modifiedBy)
		{
			if (string.IsNullOrWhiteSpace(pushApplicationId))
			{
				throw new ArgumentNullException("pushApplicationId");
			}
			if (string.IsNullOrWhiteSpace(modifiedBy))
			{
				throw new ArgumentNullException("modifiedBy");
			}
			if (expiryDate.HasValue && expiryDate < DateTime.UtcNow)
			{
				throw new ArgumentOutOfRangeException("expiryDate", "Expiry date must be in the future.");
			}

			PushApplication app = ValidatePushApplicationExists(pushApplicationId);
			if (app.Status == PushApplicationStatus.Active)
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.Active);
			}
		}

		private void ValidateSharedAddAndUpdate(PushApplication app)
		{
			if (string.IsNullOrWhiteSpace(app.Version))
			{
				throw new ArgumentNullException("Version");
			}
			if (app.Version.Length > 50)
			{
				throw new ArgumentException(
					"Version must be less than 50.",
					"Version");
			}
			if (string.IsNullOrWhiteSpace(app.Description))
			{
				throw new ArgumentNullException("Description");
			}
			if (app.Description.Length > 500)
			{
				throw new ArgumentException(
					"Description must be less than 500.",
					"Description");
			}
			if (app.MaximumDailyPushes < 0)
			{
				throw new ArgumentOutOfRangeException(
					"MaximumDailyPushes",
					app.MaximumDailyPushes,
					"Maximum daily pushes cannot be negative.");
			}
			if (app.PushPort < 1 || app.PushPort > 65535)
			{
				throw new ArgumentOutOfRangeException(
					"PushPort",
					app.PushPort,
					"Push port must be in the range of 1 thru 65535.");
			}
			if (!app.ExpiryDate.HasValue)
			{
				throw new ArgumentNullException("ExpiryDate");
			}
			if (app.StartDate > app.ExpiryDate.Value)
			{
				throw new ArgumentException("Start date must be before expiry date.");
			}
			if (app.DefaultPushLiveTime < TimeSpan.Zero)
			{
				throw new ArgumentException(
					"Default push live time cannot be negative.",
					"DefaultPushLiveTime");
			}

			if (app.Type != PushApplicationType.EnterprisePush &&
				app.BypassSubscriptions)
			{
				throw new ArgumentException(
					"Bypass subscription is only allowed for push applications of type Enterprise Push.",
					"BypassSubscriptions");
			}

			ValidateServiceLevel(app);
			ValidateStorePushRequests(app);
			ValidatePpgAddress(app);
		}

		private void ValidateServiceLevel(PushApplication app)
		{
			if (app.ServiceLevel == PushApplicationServiceLevel.PushEssentials)
			{
				if (app.IsStorePushRequests)
				{
					throw new ArgumentException(
						"Push Essentials applications cannot store requests.", "IsStorePushRequests");
				}
				if (app.IsLastNotificationEnabled)
				{
					throw new ArgumentException(
						"Push Essentials applications cannot enable last notifications.", "IsLastNotificationEnabled");
				}
				if (app.AutoDeleteRequests)
				{
					throw new ArgumentException(
						"Push Essentials applications cannot auto-delete push requests.", "AutoDeleteRequests");
				}
				if (!string.IsNullOrWhiteSpace(app.PublicNotifyUrl))
				{
					throw new ArgumentException(
						"Push Essentials applications cannot specify public notify url.", "PublicNotifyUrl");
				}
				if (!string.IsNullOrWhiteSpace(app.EnterpriseNotifyUrl))
				{
					throw new ArgumentException(
						"Push Essentials applications cannot specify enterprise notify url.", "EnterpriseNotifyUrl");
				}
			}
			else
			{
				if (app.Type == PushApplicationType.PublicPush)
				{
					if (!string.IsNullOrWhiteSpace(app.PublicNotifyUrl) &&
						app.PublicNotifyUrl.Contains("://"))
					{
						throw new ArgumentException(string.Format(
							"Public notify URL must be a relative URL. url={0}",
							app.PublicNotifyUrl));
					}
				}
				else if (app.Type == PushApplicationType.EnterprisePush)
				{
					if (!string.IsNullOrWhiteSpace(app.EnterpriseNotifyUrl) &&
						(app.EnterpriseNotifyUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
						app.EnterpriseNotifyUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase)))
					{
						throw new ArgumentException(string.Format(
							"Enterprise notify URL must be an absolute URL using http or https scheme. url={0}",
							app.EnterpriseNotifyUrl));
					}
				}
				else
				{
					if (!string.IsNullOrWhiteSpace(app.PublicNotifyUrl) &&
						app.PublicNotifyUrl.Contains("://"))
					{
						throw new ArgumentException(string.Format(
							"Public notify URL must be a relative URL. url={0}",
							app.PublicNotifyUrl));
					}

					if (!string.IsNullOrWhiteSpace(app.EnterpriseNotifyUrl) &&
						(app.EnterpriseNotifyUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
						app.EnterpriseNotifyUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase)))
					{
						throw new ArgumentException(string.Format(
							"Enterprise notify URL must be an absolute URL using http or https scheme. url={0}",
							app.EnterpriseNotifyUrl));
					}
				}
			}
		}

		private void ValidateStorePushRequests(PushApplication app)
		{
			if (app.IsStorePushRequests)
			{
				if (app.Type == PushApplicationType.PublicPush)
				{
					if (string.IsNullOrWhiteSpace(app.PublicNotifyUrl))
					{
						throw new ArgumentException(
							"Public notify URL must be specified if storing push requests.",
							"PublicNotifyUrl");
					}
				}
				else if (app.Type == PushApplicationType.EnterprisePush)
				{
					if (string.IsNullOrWhiteSpace(app.EnterpriseNotifyUrl))
					{
						throw new ArgumentException(
							"Enterprise notify URL must be specified if storing push requests.",
							"EnterpriseNotifyUrl");
					}
				}
				else
				{
					if (string.IsNullOrWhiteSpace(app.PublicNotifyUrl))
					{
						throw new ArgumentException(
							"Public notify URL must be specified if storing push requests.",
							"PublicNotifyUrl");
					}
					if (string.IsNullOrWhiteSpace(app.EnterpriseNotifyUrl))
					{
						throw new ArgumentException(
							"Enterprise notify URL must be specified if storing push requests.",
							"EnterpriseNotifyUrl");
					}
				}
			}
			else
			{
				if (app.IsLastNotificationEnabled)
				{
					throw new ArgumentException(
						"Last notification requires storing push requests.",
						"IsLastNotificationEnabled");
				}
				if (app.AutoDeleteRequests)
				{
					throw new ArgumentException(
						"Auto-delete requires storing push requests.",
						"AutoDeleteRequests");
				}
			}
		}

		private void ValidatePpgAddress(PushApplication app)
		{
			if (app.Type == PushApplicationType.PublicPush)
			{
				if (string.IsNullOrWhiteSpace(app.PublicNotifyUrl))
				{
					throw new ArgumentNullException(
						"PublicNotifyUrl",
						"Public PPG address must be specified");
				}
			}
			else if (app.Type == PushApplicationType.EnterprisePush)
			{
				if (string.IsNullOrWhiteSpace(app.EnterpriseNotifyUrl))
				{
					throw new ArgumentNullException(
						"EnterpriseNotifyUrl",
						"Enterprise PPG address must be specified");
				}
			}
			else
			{
				if (string.IsNullOrWhiteSpace(app.PublicNotifyUrl))
				{
					throw new ArgumentNullException(
						"PublicNotifyUrl",
						"Public PPG address must be specified");
				}
				if (string.IsNullOrWhiteSpace(app.EnterpriseNotifyUrl))
				{
					throw new ArgumentNullException(
						"EnterpriseNotifyUrl",
						"Enterprise PPG address must be specified");
				}
			}
		}

		private void ValidateUserNameAndPassword(PushApplication app)
		{
			if (app.Type == PushApplicationType.PublicPush)
			{
				if (string.IsNullOrWhiteSpace(app.PublicPassword))
				{
					throw new ArgumentNullException("PublicPassword");
				}
			}
			else if (app.Type == PushApplicationType.EnterprisePush)
			{
				bool userNameNull = string.IsNullOrWhiteSpace(app.UserName);
				bool passwordNull = string.IsNullOrWhiteSpace(app.EnterprisePassword);
				if (userNameNull != passwordNull)
				{
					throw new ArgumentException(
						"Both enterprise username and password should be provided or they should both be null.");
				}

				if (!passwordNull && app.EnterprisePassword.Length < 50)
				{
					throw new ArgumentException(
						"Enterprise password must be less than 50 characters.",
						"EnterprisePassword");
				}
			}
			else
			{
				if (string.IsNullOrWhiteSpace(app.PublicPassword))
				{
					throw new ArgumentNullException("PublicPassword");
				}

				bool userNameNull = string.IsNullOrWhiteSpace(app.UserName);
				bool passwordNull = string.IsNullOrWhiteSpace(app.EnterprisePassword);
				if (userNameNull != passwordNull)
				{
					throw new ArgumentException(
						"Both enterprise username and password should be provided or they should both be null.");
				}

				if (!passwordNull && app.EnterprisePassword.Length < 50)
				{
					throw new ArgumentException(
						"Enterprise password must be less than 50 characters.",
						"EnterprisePassword");
				}
			}
		} 
		#endregion

		#region IPushApplicationService Members

		/// <summary>
		/// Adds the specified push application to the persistent store.
		/// </summary>
		/// <param name="application">The application.</param>
		public void AddPushApplication(PushApplication application)
		{
			ValidateAdd(application);
			ExceptionManager.Process(
				() =>
				{
					ApplicationRepository.AddPushApplication(application);
					StatisticsRepository.AddPushStats(new PushStats(application.RimApplicationId));
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Updates the push application.
		/// </summary>
		/// <param name="pushApplication">The push application.</param>
		public void UpdatePushApplication(PushApplication pushApplication)
		{
			PushApplication pushAppFromStorage = ValidateUpdate(pushApplication);
			ExceptionManager.Process(
				() =>
				{
					ApplicationRepository.UpdatePushApplication(pushApplication);
				},
				ExceptionPolicyName);

			if (pushApplication.Status != pushAppFromStorage.Status)
			{
				if (pushApplication.Status == PushApplicationStatus.Active)
				{
					EnablePushApplication(pushApplication.RimApplicationId, pushApplication.ExpiryDate, pushApplication.ModifiedBy);
				}
				else
				{
					DisablePushApplication(pushApplication.RimApplicationId, pushApplication.ModifiedBy);
				}
			}

			if (pushApplication.Type != pushAppFromStorage.Type)
			{
				if (pushAppFromStorage.Type == PushApplicationType.PublicPush)
				{
					ChangePushApplicationTypeFromPublicToPublicAndEnterprise(
						pushApplication.RimApplicationId,
						pushApplication.UserName,
						pushApplication.EnterprisePassword,
						pushApplication.EnterpriseNotifyUrl,
						pushApplication.ModifiedBy);
				}
				else if (pushAppFromStorage.Type == PushApplicationType.EnterprisePush)
				{
					ChangePushApplicationTypeFromEnterpriseToPublicAndEnterprise(
						pushApplication.RimApplicationId,
						pushApplication.PublicPassword,
						pushApplication.PublicNotifyUrl,
						pushApplication.ModifiedBy);
				}
			}
		}

		/// <summary>
		/// Change the enterprise username and password of a push application
		/// that has a type of Enterprise Push or Public+Enterprise Push.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="currentUsername">The current username.</param>
		/// <param name="newUsername">The new username.</param>
		/// <param name="currentPassword">The current password.</param>
		/// <param name="newPassword">The new password.</param>
		/// <param name="modifiedBy">The modified by.</param>
		public void ChangePushApplicationEnterpriseCredentials(string id, string currentUsername, string newUsername, string currentPassword, string newPassword, string modifiedBy)
		{
			ExceptionManager.Process(
				() =>
				{
					ApplicationRepository.ChangePushApplicationEnterpriseCredentials(
						id, currentUsername, newUsername, currentPassword, newPassword, modifiedBy);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Change the public password of a push application that has a type of
		/// Public Push or Public+Enterprise Push.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="currentPassword">The current password.</param>
		/// <param name="newPassword">The new password.</param>
		/// <param name="modifiedBy">The modified by.</param>
		public void ChangePushApplicationPublicCredentials(string id, string currentPassword, string newPassword, string modifiedBy)
		{
			ExceptionManager.Process(
				() =>
				{
					ApplicationRepository.ChangePushApplicationPublicCredentials(
						id, currentPassword, newPassword, modifiedBy);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Changes the push application type from Enterprise to Public+Enterprise.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="publicPassword">The public password.</param>
		/// <param name="publicNotifyUrl">The public notify URL.</param>
		/// <param name="modifiedBy">The modified by.</param>
		public void ChangePushApplicationTypeFromEnterpriseToPublicAndEnterprise(string id, string publicPassword, string publicNotifyUrl, string modifiedBy)
		{
			ExceptionManager.Process(
				() =>
				{
					ApplicationRepository.ChangePushApplicationTypeFromEnterpriseToPublicAndEnterprise(
						id, publicPassword, publicNotifyUrl, modifiedBy);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Changes the push application type from Public to Public+Enterprise.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="enterpriseUsername">The enterprise username.</param>
		/// <param name="enterprisePassword">The enterprise password.</param>
		/// <param name="enterpriseNotifyUrl">The enterprise notify URL.</param>
		/// <param name="modifiedBy">The modified by.</param>
		public void ChangePushApplicationTypeFromPublicToPublicAndEnterprise(string id, string enterpriseUsername, string enterprisePassword, string enterpriseNotifyUrl, string modifiedBy)
		{
			ExceptionManager.Process(
				() =>
				{
					ApplicationRepository.ChangePushApplicationTypeFromPublicToPublicAndEnterprise(
						id, enterpriseUsername, enterprisePassword, enterpriseNotifyUrl, modifiedBy);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Deletes the push application.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="deletedBy">The deleted by.</param>
		/// <param name="unsubscribeBeforeDelete">if set to <c>true</c> [unsubscribe before delete].</param>
		/// <returns></returns>
		public async Task DeletePushApplication(string id, string deletedBy, bool unsubscribeBeforeDelete)
		{
			ValidatePushApplicationExists(id);
			try
			{
				if (unsubscribeBeforeDelete)
				{
					// Get unity container...
					IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
					UnsubscriberHelper helper = container.Resolve<UnsubscriberHelper>();
					helper.Unsubscribe(id, SubscriberStatus.Active);
					helper.Unsubscribe(id, SubscriberStatus.Suspended);
					await helper.AwaitCompletion();
				}

				// Delete all the subscriptions
				SubscriptionService.DeleteSubscriptions(id, deletedBy);

				// Delete the application
				ApplicationRepository.DeletePushApplication(id, deletedBy);
			}
			catch (Exception ex)
			{
				ExceptionManager.HandleException(ex, ExceptionPolicyName);
			}
		}

		/// <summary>
		/// Disables the push application.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="modifiedBy">The modified by.</param>
		public void DisablePushApplication(string id, string modifiedBy)
		{
			ValidateDisable(id, modifiedBy);
			ExceptionManager.Process(
				() =>
				{
					ApplicationRepository.DisablePushApplication(id, modifiedBy);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Enables the push application.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="expiryDate">The expiry date.</param>
		/// <param name="modifiedBy">The modified by.</param>
		public void EnablePushApplication(string id, DateTime? expiryDate, string modifiedBy)
		{
			ValidateEnable(id, expiryDate, modifiedBy);
			ExceptionManager.Process(
				() =>
				{
					if (expiryDate.HasValue)
					{
						ApplicationRepository.EnablePushApplication(id, expiryDate.Value, modifiedBy);
					}
					else
					{
						ApplicationRepository.EnablePushApplication(id, modifiedBy);
					}
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Gets a list of all the push applications in storage.
		/// </summary>
		/// <returns></returns>
		public IEnumerable<PushApplication> FindAll()
		{
			return ExceptionManager.Process(
				() =>
				{
					return ApplicationRepository.FindAll();
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Finds the push application with the specified id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns>
		/// A <see cref="PushApplication"/> or <c>null</c> if no push
		/// application is found.
		/// </returns>
		public PushApplication FindById(Guid id)
		{
			return ExceptionManager.Process(
				() =>
				{
					return ApplicationRepository.FindById(id);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Finds the push application with the specified application-id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns>
		/// A <see cref="PushApplication"/> or <c>null</c> if no push
		/// application is found.
		/// </returns>
		public PushApplication FindByAppId(string id)
		{
			return ExceptionManager.Process(
				() =>
				{
					return ApplicationRepository.FindByAppId(id);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Finds the by id and validate.
		/// </summary>
		/// <param name="pushApplicationId">The push application id.</param>
		/// <returns></returns>
		public PushApplication FindByIdAndValidate(string pushApplicationId)
		{
			PushApplication app = FindByAppId(pushApplicationId);
			if (app == null)
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.DoesNotExist);
			}

			if (app.Status != PushApplicationStatus.Active)
			{
				throw new InvalidPushAppException(InvalidPushAppErrorCode.Inactive);
			}

			if (app.IsExpired)
			{
				DisablePushApplication(pushApplicationId, "SYSTEM");
				throw new InvalidPushAppException(InvalidPushAppErrorCode.Expired);
			}

			return app;
		}

		/// <summary>
		/// Finds the push application with the specified name.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns>
		/// A <see cref="PushApplication"/> or <c>null</c> if no push
		/// application is found.
		/// </returns>
		public PushApplication FindByName(string name)
		{
			return ExceptionManager.Process(
				() =>
				{
					return ApplicationRepository.FindByName(name);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Finds a list of push applications that are in the specified state.
		/// </summary>
		/// <param name="status">The status.</param>
		/// <returns></returns>
		public IEnumerable<PushApplication> FindByStatus(PushApplicationStatus status)
		{
			return ExceptionManager.Process(
				() =>
				{
					return ApplicationRepository.FindByStatus(status);
				},
				ExceptionPolicyName);
		}

		/// <summary>
		/// Finds a list of push applications that are of the specified type.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		public IEnumerable<PushApplication> FindByType(PushApplicationType type)
		{
			return ExceptionManager.Process(
				() =>
				{
					return ApplicationRepository.FindByType(type);
				},
				ExceptionPolicyName);
		}

		#endregion
	}
}
